package com.nc.mem.serial.graph.cust;

import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import com.nc.mem.serial.Serializer;
import com.nc.mem.serial.graph.Context;
import com.nc.mem.serial.graph.GraphSerializer;
import com.nc.mem.serial.graph.Representation;
import com.nc.mem.serial.graph.asm.GraphSerializerFactory;
import com.nc.mem.serial.graph.boot.RegistrationListener;
import com.nc.mem.serial.graph.common.MapSerializer;
import com.nc.mem.serial.graph.container.CollectionRegistry;
import com.nc.mem.serial.graph.container.SerializerRegistry;

public class FixedKeyValMapSerializer extends GraphSerializer implements RegistrationListener {

	public static FixedKeyValMapSerializer INSTANCE = new FixedKeyValMapSerializer();

	private FixedKeyValMapSerializer() {
	}

	@SuppressWarnings("unchecked")
	@Override
	public void doRead(final ByteBuffer src, final Object obj) {
		final Map<Object, Object> objs = (Map<Object, Object>) obj;

		final int sz = (int) (Representation.readLong(src) >> 1);

		if (sz > 0) {
			final Class<?> kClazz = Representation.idToClass(src);
			final Class<?> vClazz = Representation.idToClass(src);

			final Serializer kSer = GraphSerializerFactory.serializer(kClazz);
			final Serializer vSer = GraphSerializerFactory.serializer(vClazz);

			if (kSer instanceof GraphSerializer && vSer instanceof GraphSerializer) {
				final GraphSerializer kgs = (GraphSerializer) kSer;
				final GraphSerializer vgs = (GraphSerializer) vSer;
				int s;
				Object key;
				Object val;

				final byte flags = src.get();

				final boolean tagKeys = (flags & 1) != 0;
				final boolean tagValues = (flags & 2) != 0;

				final Context context = Context.current().start();
				try {
					if (tagKeys) {
						if (tagValues) {
							for (int i = 0; i < sz; i++) {
								s = Representation.readInt(src);
								if ((s & 1 << 1) != 0) {
									key = context.fromId(s);
								} else {
									key = kgs.instantiate(src);
									context.registerId(key, s);
									kgs.doRead(src, key);
								}

								s = Representation.readInt(src);
								if ((s & 1 << 1) != 0) {
									val = context.fromId(s);
								} else {
									val = vgs.instantiate(src);
									context.registerId(val, s);
									vgs.doRead(src, val);
								}

								objs.put(key, val);
							}
						} else {
							for (int i = 0; i < sz; i++) {
								s = Representation.readInt(src);
								if ((s & 1 << 1) != 0) {
									key = context.fromId(s);
								} else {
									key = kgs.instantiate(src);
									context.registerId(key, s);
									kgs.doRead(src, key);
								}

								val = vgs.instantiate(src);
								vgs.doRead(src, val);

								objs.put(key, val);
							}
						}
					} else {
						if (tagValues) {
							for (int i = 0; i < sz; i++) {
								key = kgs.instantiate(src);
								kgs.doRead(src, key);

								s = Representation.readInt(src);
								if ((s & 1 << 1) != 0) {
									val = context.fromId(s);
								} else {
									val = vgs.instantiate(src);
									context.registerId(val, s);
									vgs.doRead(src, val);
								}

								objs.put(key, val);
							}
						} else {
							for (int i = 0; i < sz; i++) {
								key = kgs.instantiate(src);
								kgs.doRead(src, key);

								val = vgs.instantiate(src);
								vgs.doRead(src, val);

								objs.put(key, val);
							}
						}
					}
				} finally {
					context.clear();
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void doWrite(final ByteBuffer dest, final Object obj) {
		final MapHolder<Object, Object> map = (MapHolder<Object, Object>) obj;
		final int sz = map.size();
		// consumed by instantiate
		Representation.writeClassId(dest, map.mapClass());
		map.writeKindAndSize(dest);

		if (sz > 0) {
			final Iterator<Entry<Object, Object>> itr = map.iterator();
			Entry<Object, Object> e = itr.next();
			Object key = e.getKey();
			Object val = e.getValue();

			if (key == null || val == null) {
				throw new UnsupportedOperationException("Serializer does not support nulls!");
			}

			final Class<?> kClazz = key.getClass();
			final Class<?> vClazz = val.getClass();

			Representation.writeClassId(dest, kClazz);
			Representation.writeClassId(dest, vClazz);

			final Serializer kSer = GraphSerializerFactory.serializer(kClazz);
			final Serializer vSer = GraphSerializerFactory.serializer(vClazz);

			if (kSer instanceof GraphSerializer && vSer instanceof GraphSerializer) {
				final GraphSerializer kgs = (GraphSerializer) kSer;
				final GraphSerializer vgs = (GraphSerializer) vSer;
				Representation.writeByte(dest, map.flags);

				final boolean tagKeys = map.isKeyOnlyPayload();
				final boolean tagVals = map.isValOnlyPayload();

				final Context context = Context.current().start();
				int s;
				try {
					if (tagKeys) {
						if (tagVals) {
							// full tagged branch
							do {
								s = context.registerObject(key);
								Representation.writeInt(dest, s);
								if ((s & 1 << 1) == 0) {
									kgs.doWrite(dest, key);
								}

								s = context.registerObject(val);
								Representation.writeInt(dest, s);
								if ((s & 1 << 1) == 0) {
									vgs.doWrite(dest, val);
								}
							} while (itr.hasNext() && (e = itr.next()) != null && (key = e.getKey()) != null && (val = e.getValue()) != null);
						} else {
							// key only tagged branch
							do {
								s = context.registerObject(key);
								Representation.writeInt(dest, s);
								if ((s & 1 << 1) == 0) {
									kgs.doWrite(dest, key);
								}

								vgs.doWrite(dest, val);
							} while (itr.hasNext() && (e = itr.next()) != null && (key = e.getKey()) != null && (val = e.getValue()) != null);
						}
					} else {
						if (tagVals) {
							// val only tagged branch
							do {
								kgs.doWrite(dest, key);

								s = context.registerObject(val);
								Representation.writeInt(dest, s);
								if ((s & 1 << 1) == 0) {
									vgs.doWrite(dest, val);
								}
							} while (itr.hasNext() && (e = itr.next()) != null && (key = e.getKey()) != null && (val = e.getValue()) != null);
						} else {
							// no tagged branch
							do {
								kgs.doWrite(dest, key);
								vgs.doWrite(dest, val);
							} while (itr.hasNext() && (e = itr.next()) != null && (key = e.getKey()) != null && (val = e.getValue()) != null);
						}
					}
				} finally {
					context.clear();
				}
			}
		}
	}

	@Override
	public Object instantiate(final ByteBuffer src) {
		final int id = Representation.readInt(src);
		final int p = src.position();
		final long szAndKind = Representation.readLong(src);

		Object ret;

		if ((szAndKind & 1) != 0) {
			final int s = Representation.readInt(src);
			final Context ctx = Context.current();

			Comparator<?> c = (Comparator<?>) ctx.fromId(s);

			if (c == null) {
				c = (Comparator<?>) GraphSerializerFactory.serializer(Representation.idToClass(src)).read(src, s);
			}

			ret = CollectionRegistry.INSTANCE.newMap(id, c);
		} else {
			src.position(p);
			ret = CollectionRegistry.INSTANCE.newMap(id, (int) (szAndKind >> 1));
		}

		return ret;
	}

	@Override
	public void onClassRegistered(final Class<?> clazz, final int id) {
		SerializerRegistry repo;
		if (Map.class.isAssignableFrom(clazz) && !(repo = SerializerRegistry.getInstance()).hasAlternative(clazz)) {
			if (!Modifier.isAbstract(clazz.getModifiers())) {
				repo.register(clazz, MapSerializer.INSTANCE);
			}

			Class<?> c = clazz;
			while (c != null && c != Object.class && Map.class.isAssignableFrom(c) && repo.registerAlternative(c, INSTANCE)) {
				c = c.getSuperclass();
			}
			c = clazz;
			while (c != null && c != Object.class) {
				final Class<?>[] interfaces = c.getInterfaces();

				for (final Class<?> intf : interfaces) {
					if (Map.class.isAssignableFrom(intf)) {
						repo.registerAlternative(intf, INSTANCE);
					}
				}
				c = c.getSuperclass();
			}
		}
	}
}